File: /var/www/kevin-demo/wp-content/plugins/wpforms-sendinblue/src/Provider/Settings/FormBuilder.php
<?php
namespace WPFormsSendinblue\Provider\Settings;
use Exception;
use WPFormsSendinblue\Plugin;
use WPFormsSendinblue\Api\Connection;
use WPForms\Providers\Provider\Settings\FormBuilder as FormBuilderAbstract;
/**
* Class FormBuilder handles functionality inside the form builder.
*
* @since 1.0.0
*/
class FormBuilder extends FormBuilderAbstract {
/**
* Lists of account fetched from Sendinblue.
*
* @since 1.1.0
*
* @var null|array
*/
private $sendinblue_lists = null;
/**
* Register all hooks (actions and filters).
*
* @since 1.0.0
*/
protected function init_hooks() {
parent::init_hooks();
// AJAX-event names.
static $ajax_events = [
'ajax_account_save',
'ajax_account_template_get',
'ajax_connections_get',
'ajax_accounts_get',
'ajax_action_data_get',
];
// Register callbacks for AJAX events.
array_walk(
$ajax_events,
static function ( $ajax_event, $key, $instance ) {
add_filter(
"wpforms_providers_settings_builder_{$ajax_event}_{$instance->core->slug}",
[ $instance, $ajax_event ]
);
},
$this
);
// Register callbacks for hooks.
add_filter( 'wpforms_save_form_args', [ $this, 'save_form' ], 11, 2 );
}
/**
* Pre-process provider data before saving it in form_data when editing form.
*
* @since 1.0.0
*
* @param array $form Form array, usable with wp_update_post.
* @param array $data Data retrieved from $_POST and processed.
*
* @return array
*/
public function save_form( $form, $data ) {
// Get a filtered (or modified by another addon) form content.
$form_data = json_decode( stripslashes( $form['post_content'] ), true );
// Provider exists.
if ( ! empty( $form_data['providers'][ Plugin::SLUG ] ) ) {
$modified_post_content = $this->modify_form_data( $form_data );
if ( ! empty( $modified_post_content ) ) {
$form['post_content'] = wpforms_encode( $modified_post_content );
return $form;
}
}
/*
* This part works when modification is locked or current filter was called on NOT Providers panel.
* Then we need to restore provider connections from the previous form content.
*/
// Get a "previous" form content (current content is still not saved).
$prev_form = ! empty( $data['id'] ) ? wpforms()->obj( 'form' )->get( $data['id'], [ 'content_only' => true ] ) : [];
if ( ! empty( $prev_form['providers'][ Plugin::SLUG ] ) ) {
$provider = $prev_form['providers'][ Plugin::SLUG ];
if ( ! isset( $form_data['providers'] ) ) {
$form_data = array_merge( $form_data, [ 'providers' => [] ] );
}
$form_data['providers'] = array_merge( (array) $form_data['providers'], [ Plugin::SLUG => $provider ] );
$form['post_content'] = wpforms_encode( $form_data );
}
return $form;
}
/**
* Prepare modifications for the form content, if it's not locked.
*
* @since 1.0.0
*
* @param array $form_data Form content.
*
* @return array
*/
protected function modify_form_data( $form_data ) {
/**
* Connection is locked.
* Why? User clicked the "Save" button when one of AJAX requests
* for retrieving data from API was in progress or failed.
*/
if (
isset( $form_data['providers'][ Plugin::SLUG ]['__lock__'] ) &&
absint( $form_data['providers'][ Plugin::SLUG ]['__lock__'] ) === 1
) {
return [];
}
// Modify content as we need, done by reference.
foreach ( $form_data['providers'][ Plugin::SLUG ] as $connection_id => $connection ) {
if ( '__lock__' === $connection_id ) {
unset( $form_data['providers'][ Plugin::SLUG ]['__lock__'] );
continue;
}
$form_data['providers'][ Plugin::SLUG ][ $connection_id ] = $this->sanitize_connection( $connection );
}
return $form_data;
}
/**
* Sanitize connection.
*
* @since 1.0.0
*
* @param array $connection_data Connection data.
*
* @return array
*/
private function sanitize_connection( $connection_data ) {
if ( ! empty( $connection_data['fields_required'] ) ) {
$connection_data['fields_required'] = $this->sanitize_required_fields( $connection_data['fields_required'] );
}
if ( ! empty( $connection_data['fields_meta'] ) ) {
$connection_data['fields_meta'] = $this->sanitize_custom_fields( $connection_data['fields_meta'] );
}
if ( ! empty( $connection_data['event_name'] ) ) {
$connection_data['event_name'] = wpforms_sanitize_text_deeply( $connection_data['fields_meta'] );
}
return $connection_data;
}
/**
* Sanitize required fields.
*
* @since 1.0.0
*
* @param array $fields List of fields.
*
* @return array
*/
private function sanitize_required_fields( $fields ) {
foreach ( $fields as $key => &$field ) {
foreach ( $field as $name => &$value ) {
if ( wpforms_is_empty_string( $value ) ) {
unset( $fields[ $key ] );
continue;
}
$value = $name === 'field_id' || $name === 'list_id' ? absint( $value ) : sanitize_text_field( $value );
}
}
return $fields;
}
/**
* Sanitize custom fields.
*
* @since 1.0.0
*
* @param array $fields List of fields.
*
* @return array
*/
private function sanitize_custom_fields( $fields ) {
if ( empty( $fields ) ) {
return [];
}
foreach ( $fields as $key => &$field ) {
// Remove field if name or field_id is empty.
if ( wpforms_is_empty_string( $field['name'] ) || wpforms_is_empty_string( $field['field_id'] ) ) {
unset( $fields[ $key ] );
continue;
}
$field['name'] = wpforms_sanitize_text_deeply( $field['name'] );
$field['field_id'] = sanitize_text_field( $field['field_id'] );
}
return $fields;
}
/**
* Save the data for a new account and validate it.
*
* @since 1.0.0
*/
public function ajax_account_save() {
$data = wp_unslash( wp_parse_args( $_POST['data'] ) ); // phpcs:ignore
$api_key = ! empty( $data['api_key'] ) ? sanitize_text_field( $data['api_key'] ) : '';
$account_name = ! empty( $data['account_name'] ) ? sanitize_text_field( $data['account_name'] ) : '';
try {
wpforms_sendinblue()
->get( 'account' )
->add( $api_key, $account_name );
wp_send_json_success();
} catch ( Exception $e ) {
wp_send_json_error(
[
'error_msg' => esc_html__( 'Invalid Brevo API credentials. Please check your information and try again.', 'wpforms-sendinblue' ),
]
);
}
}
/**
* Content for Add New Account modal.
*
* @since 1.0.0
*
* @return array
*/
public function ajax_account_template_get() {
return [
'title' => esc_html__( 'New Brevo Account', 'wpforms-sendinblue' ),
'content' => wpforms_sendinblue()->get( 'account' )->get_form(),
'connection' => '',
'type' => 'blue',
];
}
/**
* Get the list of all saved connections.
*
* @since 1.0.0
*
* @return array
*/
public function ajax_connections_get() {
$connections = [
'connections' => ! empty( $this->get_connections_data() ) ? array_reverse( $this->get_connections_data(), true ) : [],
'conditionals' => [],
];
// Get conditional logic for each connection ID.
foreach ( $connections['connections'] as $connection ) {
if ( empty( $connection['id'] ) ) {
continue;
}
// This will either return an empty placeholder or complete set of rules, as a DOM.
$connections['conditionals'][ $connection['id'] ] = wpforms_conditional_logic()
->builder_block(
[
'form' => $this->form_data,
'type' => 'panel',
'parent' => 'providers',
'panel' => Plugin::SLUG,
'subsection' => $connection['id'],
// TODO: Since WPForms 1.9.2, this property will be redundant and can be removed once addon requirements are updated.
'reference' => esc_html__( 'Marketing provider connection', 'wpforms-sendinblue' ),
],
false
);
}
$accounts = $this->ajax_accounts_get();
return array_merge( $connections, $accounts );
}
/**
* Get the list of all accounts.
*
* @since 1.0.0
*
* @return array May return an empty sub-array.
*/
public function ajax_accounts_get() {
return [
'accounts' => $this->get_accounts(),
'actions' => $this->get_actions(),
];
}
/**
* Retrieve saved provider accounts data.
*
* @since 1.0.0
*
* @return array
*/
private function get_accounts() {
return wpforms_sendinblue()->get( 'account' )->get_accounts();
}
/**
* Retrieve saved provider connections data.
*
* @since 1.0.0
*
* @return array
*/
public function get_connections_data() {
return isset( $this->form_data['providers'][ Plugin::SLUG ] )
? $this->form_data['providers'][ Plugin::SLUG ]
: [];
}
/**
* Get all lists for account.
*
* @since 1.0.0
* @since 1.1.0 Remove the usage of transient to cache the lists.
* Instead, use `$this->sendinblue_lists` to hold the lists.
*
* @param string $account_id Account ID.
*
* @return array
*/
private function get_lists( $account_id ) {
if ( ! is_null( $this->sendinblue_lists ) ) {
return $this->sendinblue_lists;
}
$connection = wpforms_sendinblue()
->get( 'account' )
->get_connection( $account_id );
if ( ! $connection ) {
$this->sendinblue_lists = [];
return [];
}
$lists = $this->get_all_lists( $connection );
if ( empty( $lists ) ) {
$this->sendinblue_lists = [];
return [];
}
$lists = wp_list_pluck( $lists, 'name', 'id' );
// Delete an auxiliary list.
$default_key = array_search( 'identified_contacts', $lists, true );
if ( $default_key ) {
unset( $lists[ $default_key ] );
}
$this->sendinblue_lists = $lists;
return $lists;
}
/**
* Get all lists.
*
* @since 1.0.0
*
* @param Connection $connection Get all lists.
*
* @return array
*/
private function get_all_lists( $connection ) {
$lists = [];
$offset = 0;
$per_page = 50; // Is max per page value.
try {
do {
$body = $connection
->get_lists( $per_page, $offset )
->get_body();
if ( empty( $body['lists'] ) ) {
return [];
}
$lists = array_merge( $lists, $body['lists'] );
$count = ! empty( $body['count'] ) ? $body['count'] : 0;
$offset += $per_page;
} while ( $offset < $count );
} catch ( Exception $e ) {
// Redefine the list as an empty array because on the previous
// do/while block only the 2nd or 3rd iteration could generate an exception.
// We are not sure what will be in that $lists (that is partially filled),
// thus we are defining it here as an empty array again.
$lists = [];
}
return $lists;
}
/**
* Get all opt-in templates for account.
*
* @since 1.1.0
*
* @param string $account_id Account ID.
*
* @return array
*/
private function get_opt_in_templates( $account_id ) {
$connection = wpforms_sendinblue()
->get( 'account' )
->get_connection( $account_id );
if ( ! $connection ) {
return [];
}
$templates = $this->get_all_opt_in_templates( $connection );
if ( empty( $templates ) ) {
return [];
}
$templates = array_filter( $templates, [ $this, 'is_opt_in_template' ] );
return wp_list_pluck( $templates, 'name', 'id' );
}
/**
* Is it an opt-in template?
*
* @since 1.1.0
*
* @param array $template Template data.
*
* @return bool
*
* @see https://help.sendinblue.com/hc/en-us/articles/360019540880
*/
private function is_opt_in_template( $template ) {
if ( empty( $template['tag'] ) || empty( $template['htmlContent'] ) ) {
return false;
}
/**
* For opt-in templates required the `optin` tag.
*
* @see https://help.sendinblue.com/hc/en-us/articles/360019540880
*/
return $template['tag'] === 'optin' && strpos( $template['htmlContent'], '{{ doubleoptin }}' ) !== false;
}
/**
* Get all templates.
*
* @since 1.1.0
*
* @param Connection $connection Get all lists.
*
* @return array
*/
private function get_all_opt_in_templates( $connection ) {
$templates = [];
$offset = 0;
$per_page = 1000; // Is max per page value.
try {
do {
$body = $connection
->get_active_templates( $per_page, $offset )
->get_body();
if ( empty( $body['templates'] ) ) {
return [];
}
$templates = array_merge( $templates, $body['templates'] );
$count = ! empty( $body['count'] ) ? $body['count'] : 0;
$offset += $per_page;
} while ( $offset < $count );
} catch ( Exception $e ) {
// Redefine templates as an empty array because on the previous
// do/while block only the 2nd or 3rd iteration could generate an exception.
// We are not sure what will be in that $templates (that is partially filled),
// thus we are defining it here as an empty array again.
$templates = [];
}
return $templates;
}
/**
* Get list of attributes for account.
*
* @since 1.0.0
*
* @param string $account_id Account ID.
*
* @return array
*/
private function get_attributes( $account_id ) {
$connection = wpforms_sendinblue()
->get( 'account' )
->get_connection( $account_id );
if ( ! $connection ) {
return [];
}
$attributes = $connection->get_attributes();
$body = $attributes->get_body();
if ( empty( $body['attributes'] ) ) {
return [];
}
$attributes = [];
foreach ( $body['attributes'] as $attribute ) {
$attribute = (array) $attribute;
// Service has a global category as auxiliary types.
if ( $attribute['category'] === 'global' ) {
continue;
}
$attributes[ $attribute['name'] ] = $attribute['name'];
}
return $attributes;
}
/**
* Retrieve Sendinblue action data.
*
* @since 1.0.0
*
* @return array
*/
public function ajax_action_data_get() {
if ( empty( $_POST['account_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
return [];
}
$account_id = sanitize_text_field( wp_unslash( $_POST['account_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( ! wpforms_sendinblue()->get( 'account' )->account_exists( $account_id ) ) {
return [];
}
return [
'subscribe' => $this->get_subscribe_fields( $account_id ),
'unsubscribe' => $this->get_unsubscribe_fields( $account_id ),
'delete' => $this->get_delete_fields(),
'track_event' => $this->get_track_event_fields(),
];
}
/**
* Get fields for the subscribe action.
*
* @since 1.0.0
*
* @param string $account_id Account ID.
*
* @return array
*/
private function get_subscribe_fields( $account_id ) {
return [
'required' => [
'email' => [
'label' => esc_html__( 'Email', 'wpforms-sendinblue' ),
'type' => 'email_select',
'required' => true,
],
'new_email' => [
'label' => esc_html__( 'New Email', 'wpforms-sendinblue' ),
'type' => 'email_select',
],
'list' => [
'label' => esc_html__( 'List', 'wpforms-sendinblue' ),
'type' => 'select',
'required' => true,
'options' => $this->get_lists( $account_id ),
],
'opt_in' => [
'label' => esc_html__( 'Enable double opt-in', 'wpforms-sendinblue' ),
'type' => 'toggle',
'desc' => esc_html__( 'Send contacts an opt-in confirmation email when they subscribe to audience.', 'wpforms-sendinblue' ),
],
'template_id' => [
'label' => esc_html__( 'Template', 'wpforms-sendinblue' ),
'type' => 'select',
'required' => true,
'options' => $this->get_opt_in_templates( $account_id ),
],
'redirect_url' => [
'label' => esc_html__( 'Redirect URL', 'wpforms-sendinblue' ),
'type' => 'url',
'required' => true,
'placeholder' => 'https://',
'desc' => esc_html__( 'URL of the web page that the user will be redirected to after clicking on the double opt-in button (button with the {{ doubleoptin }} tag).', 'wpforms-sendinblue' ),
],
],
'optional' => $this->get_attributes( $account_id ),
];
}
/**
* Get fields for the unsubscribe action.
*
* @since 1.0.0
*
* @param string $account_id Account ID.
*
* @return array
*/
private function get_unsubscribe_fields( $account_id ) {
return [
'required' => [
'email' => [
'label' => esc_html__( 'Email', 'wpforms-sendinblue' ),
'type' => 'email_select',
'required' => true,
],
'list' => [
'label' => esc_html__( 'List', 'wpforms-sendinblue' ),
'type' => 'select',
'required' => true,
'options' => $this->get_lists( $account_id ),
],
],
'optional' => [],
];
}
/**
* Get fields for the delete action.
*
* @since 1.0.0
*
* @return array
*/
private function get_delete_fields() {
return [
'required' => [
'email' => [
'label' => esc_html__( 'Email', 'wpforms-sendinblue' ),
'type' => 'email_select',
'required' => true,
],
],
'optional' => [],
];
}
/**
* Get fields for the track event action.
*
* @since 1.0.0
*
* @return array
*/
private function get_track_event_fields() {
return [
'required' => [
'email' => [
'label' => esc_html__( 'Email', 'wpforms-sendinblue' ),
'type' => 'email_select',
'required' => true,
],
'event_name' => [
'label' => esc_html__( 'Event name', 'wpforms-sendinblue' ),
'type' => 'text',
'required' => true,
],
],
'optional' => [],
];
}
/**
* Get list of actions.
*
* @since 1.0.0
*
* @return array
*/
private function get_actions() {
return [
'subscribe' => esc_html__( 'Subscribe', 'wpforms-sendinblue' ),
'unsubscribe' => esc_html__( 'Unsubscribe', 'wpforms-sendinblue' ),
'delete' => esc_html__( 'Delete', 'wpforms-sendinblue' ),
'track_event' => esc_html__( 'Track Event', 'wpforms-sendinblue' ),
];
}
/**
* Use this method to register own templates for form builder.
* Make sure, that you have `tmpl-` in template name in `<script id="tmpl-*">`.
*
* @since 1.0.0
*/
public function builder_custom_templates() {
?>
<!-- Single Sendinblue connection. -->
<script type="text/html" id="tmpl-wpforms-<?php echo esc_attr( Plugin::SLUG ); ?>-builder-content-connection">
<?php echo wpforms_sendinblue()->get( 'template' )->get_builder_template( 'connection' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</script>
<!-- Single Sendinblue connection block: REQUIRED EMAIL SELECT FIELD. -->
<script type="text/html" id="tmpl-wpforms-<?php echo esc_attr( Plugin::SLUG ); ?>-builder-content-connection-required-email-select-field">
<?php echo wpforms_sendinblue()->get( 'template' )->get_builder_template( 'required-email-select-field' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</script>
<!-- Single Sendinblue connection block: REQUIRED SELECT FIELD. -->
<script type="text/html" id="tmpl-wpforms-<?php echo esc_attr( Plugin::SLUG ); ?>-builder-content-connection-required-select-field">
<?php echo wpforms_sendinblue()->get( 'template' )->get_builder_template( 'required-select-field' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</script>
<!-- Single Sendinblue connection block: REQUIRED TEXT FIELD. -->
<script type="text/html" id="tmpl-wpforms-<?php echo esc_attr( Plugin::SLUG ); ?>-builder-content-connection-required-text-field">
<?php echo wpforms_sendinblue()->get( 'template' )->get_builder_template( 'required-text-field' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</script>
<!-- Single Sendinblue connection block: REQUIRED CHECKBOX FIELD. -->
<script type="text/html" id="tmpl-wpforms-<?php echo esc_attr( Plugin::SLUG ); ?>-builder-content-connection-required-toggle-field">
<?php echo wpforms_sendinblue()->get( 'template' )->get_builder_template( 'required-toggle-field' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</script>
<!-- Single Sendinblue connection block: REQUIRED CHECKBOX FIELD. -->
<script type="text/html" id="tmpl-wpforms-<?php echo esc_attr( Plugin::SLUG ); ?>-builder-content-connection-required-url-field">
<?php echo wpforms_sendinblue()->get( 'template' )->get_builder_template( 'required-url-field' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</script>
<!-- Single Sendinblue connection block: ERROR. -->
<script type="text/html" id="tmpl-wpforms-<?php echo esc_attr( Plugin::SLUG ); ?>-builder-content-connection-error">
<?php echo wpforms_sendinblue()->get( 'template' )->get_builder_template( 'error' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</script>
<?php
}
/**
* Enqueue JavaScript and CSS files.
*
* @since 1.0.0
*/
public function enqueue_assets() {
parent::enqueue_assets();
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-sendinblue-admin-builder',
WPFORMS_SENDINBLUE_URL . "assets/js/sendinblue-builder{$min}.js",
[ 'wpforms-admin-builder-providers' ],
WPFORMS_SENDINBLUE_VERSION,
true
);
wp_localize_script(
'wpforms-sendinblue-admin-builder',
'wpformsSendinblueBuilderVars',
[
'l10n' => [
'provider_placeholder' => esc_html__( '--- Select Brevo Field ---', 'wpforms-sendinblue' ),
// The `nameFieldFormats` key is deprecated since 1.5.0.
'nameFieldFormats' => [
'full' => esc_html__( 'Full', 'wpforms-sendinblue' ),
'first' => esc_html__( 'First', 'wpforms-sendinblue' ),
'middle' => esc_html__( 'Middle', 'wpforms-sendinblue' ),
'last' => esc_html__( 'Last', 'wpforms-sendinblue' ),
],
],
]
);
}
}